﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Expect.VisualStudio.Extensions;
using Expect.VisualStudio.Extensions.Commands;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Shell;

namespace Expect.Vs2010.Extensions.Tagging
{
    class FixtureSmartTagger : ITagger<SmartTag>, IDisposable
    {
        private ITextBuffer _buffer;
        private ITextView _textView;
        private bool disposed = false;
        private IExpectVSService _expectService;
        
        public FixtureSmartTagger(ITextBuffer buffer, ITextView textView)
        {
            _buffer = buffer;
            _textView = textView;
            _expectService = (IExpectVSService)Package.GetGlobalService(typeof(SExpectVSService));
            
            _textView.LayoutChanged += OnLayoutChanged;
            _textView.Selection.SelectionChanged += new EventHandler(Selection_SelectionChanged);
        }

        void Selection_SelectionChanged(object sender, EventArgs e)
        {
            this.RefreshAll();
        }

        private void ProcessTags(object sender, TagsChangedEventArgs args)
        {
            foreach (var span in args.Span.GetSpans(_buffer))
                RaiseTagsChangedEvent(span);
        }

        /// <summary>
        /// Returns tags on demand.
        /// </summary>
        /// <param name="spans">Spans collection to get tags for.</param>
        /// <returns>Squiggle tags in provided spans.</returns>
        public IEnumerable<ITagSpan<SmartTag>> GetTags(NormalizedSnapshotSpanCollection spans)
        {
            if (spans.Count == 0)
                yield break;

            var item = _expectService.GetCurrentItemResolution();

            if (item == null) yield break;

            foreach (var fixtureSpan in spans)
            {
                var text = fixtureSpan.GetText();
                var trimmed = text.Trim();
                var line = fixtureSpan.Start.GetContainingLine();

                if (!string.IsNullOrEmpty(trimmed))
                {
                    if (_textView.Selection.Start.Position.GetContainingLine().LineNumber != fixtureSpan.Start.GetContainingLine().LineNumber)
                    {
                        yield return
                            new TagSpan<SmartTag>(
                                fixtureSpan, new FixtureSmartTag(new List<SmartTagActionSet>().AsReadOnly()));
                    }
                    else
                    {
                        var resolution = _expectService.GetResolutionForLine(line.LineNumber);
                        if (resolution != null)
                        yield return
                            new TagSpan<SmartTag>(
                                fixtureSpan, new FixtureSmartTag(GetSmartTagActions(fixtureSpan, resolution)));
                    }
                }
            }
        }

        public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
        
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    _textView.LayoutChanged -= OnLayoutChanged;                    
                }

                disposed = true;
            }
        }

        private ReadOnlyCollection<SmartTagActionSet> GetSmartTagActions(SnapshotSpan fixtureSpan, SelectionResolution resolution)
        {
            List<SmartTagActionSet> smartTagSets = new List<SmartTagActionSet>();
            
            if (_textView.Selection.Start.Position.GetContainingLine().LineNumber != fixtureSpan.Start.GetContainingLine().LineNumber)
                return smartTagSets.AsReadOnly();

            ITrackingSpan trackingSpan = fixtureSpan.Snapshot.CreateTrackingSpan(fixtureSpan, SpanTrackingMode.EdgeExclusive);

            // Add spelling suggestions (if there are any)
            List<ISmartTagAction> actions = new List<ISmartTagAction>();
            var commands = CommandFactory.GetFixtureCommands(resolution);
            foreach (var command in commands)
            {
                actions.Add(new FixtureSmartTagAction(command, resolution));
            }

            //foreach (var suggestion in suggestions)
            //    actions.Add(new SpellSmartTagAction(trackingSpan, suggestion, true));

            if (actions.Count > 0)
                smartTagSets.Add(new SmartTagActionSet(actions.AsReadOnly()));

            // Add Dictionary operations (ignore all)
            //List<ISmartTagAction> dictionaryActions = new List<ISmartTagAction>();
            //dictionaryActions.Add(new SpellDictionarySmartTagAction(trackingSpan, _dictionary, "Ignore All"));
            //smartTagSets.Add(new SmartTagActionSet(dictionaryActions.AsReadOnly()));

            return smartTagSets.AsReadOnly();
        }


        private void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
        {
            var lines = e.NewOrReformattedLines;
            EventHandler<SnapshotSpanEventArgs> handler = this.TagsChanged;

            if (lines == null || lines.Count == 0) return;

            var snapshot = lines[0].Extent.Snapshot;
            
            SnapshotSpan span = new SnapshotSpan(snapshot, new Span(lines[0].Extent.Start, lines[0].Extent.Length));
            if (handler != null)
            {
                handler(this, new SnapshotSpanEventArgs(span));
            }
        }

        private void RefreshAll()
        {
            foreach (var textViewLine in this._textView.TextViewLines)
            {
                SnapshotSpan span = new SnapshotSpan(textViewLine.Snapshot, new Span(textViewLine.Extent.Start, textViewLine.Extent.Length));
                this.RaiseTagsChangedEvent(span);
            }
        }

        private void RaiseTagsChangedEvent(SnapshotSpan subjectSpan)
        {
            EventHandler<SnapshotSpanEventArgs> handler = this.TagsChanged;
            if (handler != null)
            {
                handler(this, new SnapshotSpanEventArgs(subjectSpan));
            }
        }
    }
}
